using System;
using System.Reflection;
using Fabrication.Exceptions;

namespace Fabrication
{
    public class DoubleLinkDetails : ILinkDetails
    {
        private readonly Func<object, object, object> sourceValueModifier;
        private readonly SourceValue sourceValue1 = new SourceValue();
        private readonly SourceValue sourceValue2 = new SourceValue();
        private readonly Type sourceOwningType;
        private readonly TypedPropertyKey sourceProperty1;
        private readonly TypedPropertyKey sourceProperty2;
        private readonly TypedPropertyKey targetProperty;

        public DoubleLinkDetails(Type sourceOwningType, Type targetOwningType, TypedPropertyKey sourceProperty1, TypedPropertyKey sourceProperty2, TypedPropertyKey targetProperty, Func<object, object, object> sourceValueModifier)
        {
            if (sourceOwningType == null ||
                targetOwningType == null ||
                sourceProperty1 == null ||
                sourceProperty2 == null ||
                targetProperty == null)
                throw new LinkedPropertyException("All parameters are required for a valid link");

            this.sourceOwningType = sourceOwningType;
            this.sourceProperty1 = sourceProperty1;
            this.sourceProperty2 = sourceProperty2;
            this.targetProperty = targetProperty;
            TargetOwningType = targetOwningType;
            this.sourceValueModifier = sourceValueModifier;
        }

        public Type TargetOwningType { get; private set; }
        public Type TargetPropertyType { get { return targetProperty.Type; } }
        public string TargetPropertyName { get { return targetProperty.Name; } }
        public bool HasSourceValues
        {
            get { return sourceValue1.IsSet && sourceValue2.IsSet; }
        }

        public void SetSourceValue(object value)
        {
            if (value == null)
            {
                if (sourceValue1.IsSet)
                {
                    sourceValue2.Value = null;
                }
                else
                {
                    sourceValue1.Value = null;
                }
            }
            else
            {
                var valueType = value.GetType();
                if (sourceProperty1.Type == sourceProperty2.Type)
                {
                    if (sourceValue1.IsSet)
                    {
                        sourceValue2.Value = value;
                    }
                    else
                    {
                        sourceValue1.Value = value;
                    }
                }

                else if (valueType == sourceProperty1.Type)
                {
                    sourceValue1.Value = value;
                }
                else if (valueType == sourceProperty2.Type)
                {
                    sourceValue2.Value = value;
                }
                else
                    throw new LinkedPropertyException(string.Format("'{0}' must be of type '{1}' or '{2}' to be a valid source value.", value, sourceProperty1.Name, sourceProperty2.Name));
            }
        }

        public object GetTargetValue()
        {
            if (!HasSourceValues)
                throw new LinkedPropertyException("Cannot generate target value before source value(s) have been set.");

            return sourceValueModifier(sourceValue1.Value, sourceValue2.Value);
        }

        public bool SourceMatches(PropertyInfo property)
        {
            return SourceMatches(property.Name, property.PropertyType, property.ReflectedType);
        }

        public bool SourceMatches(string propertyName, Type propertyType, Type owningType)
        {
            return sourceOwningType == owningType &&
                   (sourceProperty1.Type == propertyType &&
                    sourceProperty1.Name == propertyName) ||
                   (sourceProperty2.Type == propertyType &&
                    sourceProperty2.Name == propertyName);
        }

        public bool TargetMatches(PropertyInfo targetPropertyInfo)
        {
            return TargetMatches(targetPropertyInfo.Name,
                                 targetPropertyInfo.PropertyType,
                                 targetPropertyInfo.ReflectedType);
        }

        public bool TargetMatches(ILinkDetails linkDetails)
        {
            return TargetMatches(linkDetails.TargetPropertyName,
                                 linkDetails.TargetPropertyType,
                                 linkDetails.TargetOwningType);
        }

        public bool TargetMatches(string propertyName, Type propertyType, Type owningType)
        {
            return TargetOwningType == owningType &&
                   targetProperty.Type == propertyType &&
                   targetProperty.Name == propertyName;
        }
    }
}